﻿//-----------------------------------------------------------------------------------------------------------
// Wbfs Engine (WBFSe) Project by Omega Frost 
// http://wbfse.codeplex.com/
//
// WBFSe is Licensed under the terms of the 
// Microsoft Reciprocal License (Ms-RL)
//-----------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using WBFSe.Attributes;
using WBFSe.Builders;
using WBFSe.Builders.V1;
using WBFSe.Data;
using WBFSe.FormatApi;

namespace WBFSe.IO.Files
{
    /// <summary>
    /// A normal loaded wii game file
    /// </summary>
    class WbfsFileV1 : IWbfsFileInfo
    {
        // Variables

        private IWbfsFormat format;
        private WbfsReader reader;

        // Properties

        /// <summary>
        /// Indicates that the file
        /// is being created by engine
        /// and may be deleted on failure
        /// </summary>
        public bool Temporary
        {
            get { return false; }
        }

        /// <summary>
        /// Number of open streams
        /// </summary>
        public int ActiveStreams
        {
            get { return this.reader.ActiveStreams; }
        }

        /// <summary>
        /// Name of the game
        /// </summary>
        public string Name
        {
            get { return this.reader.GameName; }
        }

        /// <summary>
        /// Code of the game
        /// </summary>
        public string Code
        {
            get { return this.reader.GameCode; }
        }

        /// <summary>
        /// Region of the game
        /// </summary>
        public Region Region
        {
            get { return this.reader.GameRegion; }
        }

        /// <summary>
        /// Type of the game
        /// </summary>
        public GameType Type
        {
            get { return GameType.Wii; }
        }

        /// <summary>
        /// Physical location of the container
        /// </summary>
        public String Path
        {
            get { return this.reader.Path; }
        }

        /// <summary>
        /// First container filename
        /// </summary>
        public String Filename
        {
            get { return this.reader.Filenames[0]; }
        }

        /// <summary>
        /// Container filenames
        /// </summary>
        public IList<string> Filenames
        {
            get { return this.reader.Filenames; }
        }

        /// <summary>
        /// Physical size of the container
        /// </summary>
        public long Size
        {
            get { return this.reader.FileSize; }
        }

        /// <summary>
        /// Sector size used by the enclosing type
        /// </summary>
        public int SectorSize
        {
            get { return this.reader.SectorSize; }
        }

        /// <summary>
        /// Usage table of the enclosing type
        /// </summary>
        public UsageTable Usage
        {
            get { return this.reader.SectorUsage; }
        }

        /// <summary>
        /// Format of the container
        /// </summary>
        public IWbfsFormat Format
        {
            get { return this.format; }
        }

        // Methods

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="format">The container format</param>
        /// <param name="reader">The container reader</param>
        public WbfsFileV1(IWbfsFormat format, WbfsReader reader)
        {
            this.format = format;
            this.reader = reader;
        }

        /// <summary>
        /// Generates extended information for the game
        /// </summary>
        /// <param name="flags">Build flags</param>
        /// <param name="info">Out info</param>
        /// <returns>Disc's extended information</returns>
        /// <exception cref="System.NotImplementedException">Game type not supported</exception>
        /// <exception cref="System.FormatException">The disc inside the container is corrupted</exception>
        public IDiscExtendedInfo BuildExtendedInfo(BuildFlags flags)
        {
            IWbfsStream stream;
            IDiscBuilder db;

            stream = this.reader.Open(FileAccess.Read, FileShare.Read);
            db = new BuilderV1(stream);

            try
            {
                if (!db.Build(flags))
                    throw new FormatException("The disc format is invalid");

                stream.Close();
                return db;
            }
            catch
            {
                db.Dispose();
                stream.Close();
                throw;
            }
        }

        /// <summary>
        /// Creates a stream with desired access and share
        /// </summary>
        /// <param name="access">Access for all files of the container</param>
        /// <param name="share">Share for all files of the container</param>
        /// <returns>Stream on success</returns>
        /// <exception cref="System.Exception">On operation failure</exception>
        public IWbfsStream Open(FileAccess access, FileShare share)
        {
            return this.reader.Open(access, share);
        }

        /// <summary>
        /// Changes the game data
        /// </summary>
        /// <param name="name">New name</param>
        /// <param name="code">New code</param>
        public void ChangeDiscHeader(string name, string code)
        {
            IWbfsStream stream = this.reader.Open(FileAccess.ReadWrite,
                FileShare.Read);

            if (name.Length > WbfsConstantsV1.WiiNameLen)
                throw new ArgumentException("Name too long");

            if (code.Length > WbfsConstantsV1.WiiCodeLen)
                throw new ArgumentException("Code too long");

            UnmanagedArray block = new UnmanagedArray(this.SectorSize);
            stream.Read(block.DataPtr, 0, 0, this.SectorSize);

            Byte[] bcode = new Byte[WbfsConstantsV1.WiiCodeLen];
            Byte[] bname = new Byte[WbfsConstantsV1.WiiNameLen];

            Encoding.Default.GetBytes(code).CopyTo(bcode, 0);
            Encoding.Default.GetBytes(name).CopyTo(bname, 0);

            block.CopyFrom(bcode, 0, WbfsConstantsV1.WiiCodePos, WbfsConstantsV1.WiiCodeLen);
            block.CopyFrom(bname, 0, WbfsConstantsV1.WiiNamePos, WbfsConstantsV1.WiiNameLen);

            stream.Write(block.DataPtr, 0, 0, this.SectorSize);
            block.Dispose();

            stream.Close();
            this.reader.ChangeHeader(name, code, this.Region);
        }

        /// <summary>
        /// Extracts files from the disc
        /// </summary>
        /// <param name="partition">Partition index</param>
        /// <param name="path">Full path of the file</param>
        /// <param name="output">Stream for data output</param>
        public void ExtractFile(int partition, string path, Stream output)
        {
            IWbfsStream stream;
            IDiscBuilder db;

            stream = this.reader.Open(FileAccess.Read, FileShare.Read);
            db = new BuilderV1(stream);

            try
            {
                if (!db.Build(BuildFlags.KeepCryptData | BuildFlags.MapFilesystem))
                    throw new FormatException("The disc format is invalid");

                String[] pathTokens = path.Split(new char[] { '\\', '/' },
                    StringSplitOptions.RemoveEmptyEntries);

                FileSystemNode node = null;
                foreach (FileSystemNode pnode in db.Root.Nodes)
                {
                    if (pnode.Partition == partition)
                    {
                        node = pnode;
                        break;
                    }
                }

                if (node == null)
                    throw new FileNotFoundException("Partition nor found");

                foreach (String token in pathTokens)
                {
                    node = node.FindNode(token);
                    if (node == null)
                        throw new FileNotFoundException();
                }

                if (!db.ExtractFile(node, output))
                    throw new FormatException("The disc format is invalid");

                db.Dispose();
                stream.Close();
            }
            catch
            {
                db.Dispose();
                stream.Close();
                throw;
            }
        }
    }
}
